#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "ssd1306.h"
#include "delay.h"
#include "font.h"

#define LCD_HOST SPI2_HOST

#define PIN_NUM_CLK 4
#define PIN_NUM_MOSI 5
#define PIN_NUM_RST 6
#define PIN_NUM_DC 7
#define PIN_NUM_CS 15

#define X_WIDTH 132
#define Y_WIDTH 64

spi_device_handle_t spi;
static const char *TAG = "ssd1306";

void lcd_data(uint8_t data)
{
    esp_err_t ret;
    spi_transaction_t t;
    gpio_set_level(PIN_NUM_DC, 1);
    memset(&t, 0, sizeof(t)); // 清空结构体

    t.length = 8;        // 数据长度（以位为单位）
    t.tx_buffer = &data; // 发送的数据
    t.user = (void *)0;  // 空数据，可以设置为0
    t.flags = 0;         // 无特殊标志

    ret = spi_device_polling_transmit(spi, &t); // 发送数据
    if (ret != ESP_OK)
    {
        printf("Failed to transmit SPI data.\n");
        return;
    }
}

void lcd_cmd(uint8_t data)
{
    esp_err_t ret;
    spi_transaction_t t;
    gpio_set_level(PIN_NUM_DC, 0);
    memset(&t, 0, sizeof(t)); // 清空结构体

    t.length = 8;        // 数据长度（以位为单位）
    t.tx_buffer = &data; // 发送的数据
    t.user = (void *)0;  // 空数据，可以设置为0
    t.flags = 0;         // 无特殊标志

    ret = spi_device_polling_transmit(spi, &t); // 发送数据
    if (ret != ESP_OK)
    {
        printf("Failed to transmit SPI data.\n");
        return;
    }
}
void lcd_set_pos(unsigned char x, unsigned char y)
{
    lcd_cmd(0xb0 + y);
    lcd_cmd(((x & 0xf0) >> 4) | 0x10);
    lcd_cmd((x & 0x0f));
}

void lcd_clear(void)
{
    unsigned char y, x;
    for (y = 0; y < 8; y++)
    {
        lcd_cmd(0xb0 + y);
        lcd_cmd(0x01);
        lcd_cmd(0x10);
        for (x = 0; x < X_WIDTH; x++)
            lcd_data(0);
    }
}

//==============================================================
// 函数名： void lcd_draw_dot(unsigned char x,unsigned char y)
// 功能描述：绘制一个点（x,y）
// 参数：真实坐标值(x,y),x的范围0～127，y的范围0～64
// 返回：无
//==============================================================
void lcd_draw_dot(unsigned char x, unsigned char y)
{
    unsigned char data1; // data1当前点的数据

    lcd_set_pos(x, (unsigned char)(y >> 3));
    data1 = (unsigned char)(0x01 << (y % 8));
    lcd_cmd((unsigned char)(0xb0 + (y >> 3)));
    lcd_cmd((unsigned char)(((x & 0xf0) >> 4) | 0x10));
    lcd_cmd((unsigned char)((x & 0x0f) | 0x00));
    lcd_data(data1);
}

void lcd_clear_dot(unsigned char x)
{
    lcd_set_pos(x, 6);
    lcd_cmd((unsigned char)(0xb0 + 6));
    lcd_cmd((unsigned char)(((x & 0xf0) >> 4) | 0x10));
    lcd_cmd((unsigned char)((x & 0x0f) | 0x00));
    lcd_data(0x00);

    lcd_set_pos(x, 7);
    lcd_cmd((unsigned char)(0xb0 + 7));
    lcd_cmd((unsigned char)(((x & 0xf0) >> 4) | 0x10));
    lcd_cmd((unsigned char)((x & 0x0f) | 0x00));
    lcd_data(0x00);
}
void lcd_fill(unsigned char bmp_data)
{
    unsigned char y, x;

    for (y = 0; y < 8; y++)
    {
        lcd_cmd(0xb0 + y);
        lcd_cmd(0x01);
        lcd_cmd(0x10);
        for (x = 0; x < X_WIDTH; x++)
            lcd_data(bmp_data);
    }
}

//==============================================================
// 函数名：LCD_P6x8Str(unsigned char x,unsigned char y,unsigned char *p)
// 功能描述：写入一组标准ASCII字符串
// 参数：显示的位置（x,y），y为页范围0～7，要显示的字符串
// 返回：无
//==============================================================
void LCD_P6x8Str(unsigned char x, unsigned char y, char ch[])
{
    unsigned char c = 0, i = 0, j = 0;
    while (ch[j] != '\0')
    {
        c = ch[j] - 32;
        if (x > 126)
        {
            x = 0;
            y++;
        }
        lcd_set_pos(x, y);
        for (i = 0; i < 6; i++)
            lcd_data(F6x8[c][i]);
        x += 6;
        j++;
    }
}

void LCD_P8x16Str(unsigned char x, unsigned char y, char ch[])
{
    unsigned char c = 0, i = 0, j = 0;

    while (ch[j] != '\0')
    {
        c = ch[j] - 32;
        if (x > 120)
        {
            x = 0;
            y++;
        }
        lcd_set_pos(x, y);
        for (i = 0; i < 8; i++)
            lcd_data(font_8x16[c * 16 + i]);
        lcd_set_pos(x, y + 1);
        for (i = 0; i < 8; i++)
            lcd_data(font_8x16[c * 16 + i + 8]);
        x += 8;
        j++;
    }
}

void ssd1306_init_io_spi(void)
{
    esp_err_t ret;

    // 配置SPI总线
    spi_bus_config_t buscfg = {
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 0, // 默认值为0，表示使用默认的最大传输大小
    };
    ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
    if (ret != ESP_OK)
    {
        printf("Failed to initialize SPI bus.\n");
        return;
    }

    // 添加SPI设备
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 1 * 1000 * 1000, // SPI时钟频率
        .mode = 0,                         // SPI模式，0为模式0
        .spics_io_num = PIN_NUM_CS,        // 片选引脚
        .queue_size = 1,
        .flags = SPI_DEVICE_HALFDUPLEX, // 半双工模式
    };
    ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi);
    if (ret != ESP_OK)
    {
        printf("Failed to add SPI device.\n");
        return;
    }

    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_CS, GPIO_MODE_OUTPUT);
}


//输出汉字字符串
void LCD_P14x16Str(unsigned char x,unsigned char y, char ch[])
{
    unsigned char wm=0,ii = 0;
    unsigned int adder=1; 
    
    while(ch[ii] != '\0')
    {
        wm = 0;
        adder = 1;
        while(F14x16_Idx[wm] > 127)
        {
            if(F14x16_Idx[wm] == ch[ii])
            {
                if(F14x16_Idx[wm + 1] == ch[ii + 1])
                {
                    adder = wm * 14;
                    break;
                }
            }
            wm += 2;			
        }
        if(x>118){x=0;y++;}
        lcd_set_pos(x , y); 
        if(adder != 1)// 显示汉字					
        {
            lcd_set_pos(x , y);
            for(wm = 0;wm < 14;wm++)               
            {
                lcd_data(F14x16[adder]);	
                adder += 1;
            }      
            lcd_set_pos(x,y + 1); 
            for(wm = 0;wm < 14;wm++)          
            {
                lcd_data(F14x16[adder]);
                adder += 1;
            }   		
        }
        else			  //显示空白字符			
        {
            ii += 1;
            lcd_set_pos(x,y);
            for(wm = 0;wm < 16;wm++)
            {
                lcd_data(0);
            }
            lcd_set_pos(x,y + 1);
            for(wm = 0;wm < 16;wm++)
            {   		
                lcd_data(0);	
            }
        }
        x += 14;
        ii += 2;
    }
}

void ssd1306_init(void)
{
    ssd1306_init_io_spi();

    gpio_set_level(PIN_NUM_RST, 0);
    delay_ms(50);
    gpio_set_level(PIN_NUM_RST, 1);

    lcd_cmd(0xae); //--turn off oled panel
    lcd_cmd(0x00); //---set low column address
    lcd_cmd(0x10); //---set high column address
    lcd_cmd(0x40); //--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    lcd_cmd(0x81); //--set contrast control register
    lcd_cmd(0xcf); // Set SEG Output Current Brightness
    lcd_cmd(0xa1); //--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    lcd_cmd(0xc8); // Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    lcd_cmd(0xa6); //--set normal display
    lcd_cmd(0xa8); //--set multiplex ratio(1 to 64)
    lcd_cmd(0x3f); //--1/64 duty
    lcd_cmd(0xd3); //-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
    lcd_cmd(0x00); //-not offset
    lcd_cmd(0xd5); //--set display clock divide ratio/oscillator frequency
    lcd_cmd(0x80); //--set divide ratio, Set Clock as 100 Frames/Sec
    lcd_cmd(0xd9); //--set pre-charge period
    lcd_cmd(0xf1); // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    lcd_cmd(0xda); //--set com pins hardware configuration
    lcd_cmd(0x12);
    lcd_cmd(0xdb); //--set vcomh
    lcd_cmd(0x40); // Set VCOM Deselect Level
    lcd_cmd(0x20); //-Set Page Addressing Mode (0x00/0x01/0x02)
    lcd_cmd(0x02); //
    lcd_cmd(0x8d); //--set Charge Pump enable/disable
    lcd_cmd(0x14); //--set(0x10) disable
    lcd_cmd(0xa4); // Disable Entire Display On (0xa4/0xa5)
    lcd_cmd(0xa6); // Disable Inverse Display On (0xa6/a7)
    lcd_cmd(0xaf); //--turn on oled panel

    lcd_clear(); // 初始清屏
    lcd_fill(0xff);

    lcd_clear(); // LCD清屏
    LCD_P8x16Str(0, 0, "nihao");
    LCD_P8x16Str(0, 2, "2");
    LCD_P8x16Str(0, 4, "2");
    LCD_P8x16Str(0, 6, "2");
    LCD_P8x16Str(16, 6, "2");
}